package v1

import (
	"context"
	"encoding/json"
	model "quipus/internal/database/model"
	"quipus/internal/utils"
	v1pb "quipus/proto/gen/api/v1"
	apb "quipus/proto/gen/assist"
	"strings"

	cpb "quipus/proto/gen/common"

	"github.com/lithammer/shortuuid/v4"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
)

func (s *APIV1Service) CreateWorkflow(ctx context.Context, request *v1pb.CreateWorkflowRequest) (*v1pb.Workflow, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}
	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() && currentUser.Role != cpb.Role_TENANT_ADMIN.String() {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	err = s.convertFlowToDB(ctx, request.Workflow, 0, currentUser)

	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to convert flow to db: %v", err)
	}

	return request.Workflow, nil
}

func (s *APIV1Service) convertFlowToDB(ctx context.Context, flow *v1pb.Workflow, currrentFlow int32, user *model.User) error {
	if flow != nil {
		create := &model.Workflow{
			Name:         flow.Name,
			UID:          shortuuid.New(),
			InputLen:     flow.InputLen,
			WorkType:     flow.WorkType.String(),
			CreatorID:    user.ID,
			TenantID:     user.TenantID,
			Pos:          flow.Pos.String(),
			GranaryScope: flow.GranaryScope.String(),
			Description:  flow.Description,
		}

		if flow.Granaries != nil {
			for _, granary := range flow.Granaries {
				_, e := utils.ExtractIDFromUrn(utils.GranaryUrnPrefix, granary)
				if e != nil {
					return e
				}

			}
			b, e := json.Marshal(flow.Granaries)
			if e != nil {
				return e
			}
			create.Granaries = string(b)
		}
		if flow.Assistant != "" {
			if strings.HasPrefix(flow.Assistant, utils.AssistantUrnPrefix) {
				id, err := utils.ExtractIDFromUrn(utils.AssistantUrnPrefix, flow.Assistant)
				if err != nil {
					return err
				}
				assistant, err := s.Gds.Handler.GetLLMAssistant(ctx, id)
				if assistant == nil {
					return status.Errorf(codes.Internal, "assistant not found")
				}
			}
			create.Assistant = flow.Assistant
		}

		w, err := s.Gds.Handler.GetWorkflow(ctx, &apb.FindWorkflow{
			Name:     flow.Name,
			TenantId: user.TenantID})

		if w != nil {
			if currrentFlow == 0 {
				return status.Errorf(codes.Internal, "workflow name already exist")
			} else {
				create.ID = w.ID
			}
		} else {
			err = s.Gds.Handler.CreateWorkflow(ctx, create)
			if err != nil {
				return err
			}
		}

		flow.Urn = utils.BuildUrn(utils.WorkflowUrnPrefix, create.ID)

		if currrentFlow != 0 {
			relation := &model.WorkflowRelation{
				FlowID:     currrentFlow,
				NextFlowID: create.ID,
			}
			err = s.Gds.Handler.CreateWorkflowRelation(ctx, relation)
			if err != nil {
				return err
			}
		}

		if flow.Next == nil || len(flow.Next) < 1 {
			return nil
		}

		for _, next := range flow.Next {
			s.convertFlowToDB(ctx, next, create.ID, user)
		}

	}
	return nil
}

func (s *APIV1Service) GetWorkflow(ctx context.Context, request *v1pb.GetWorkflowRequest) (*v1pb.Workflow, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	id, err := utils.ExtractIDFromUrn(utils.WorkflowUrnPrefix, request.Urn)
	find := &apb.FindWorkflow{
		Id: id,
	}
	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		find.TenantId = currentUser.TenantID
	}

	workflowModel, err := s.Gds.Handler.GetWorkflow(ctx, find)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get workflow: %v", err)
	}
	return s.buildWorkFlow(ctx, workflowModel)

}

func (s *APIV1Service) buildWorkFlow(ctx context.Context, flowModel *model.Workflow) (*v1pb.Workflow, error) {
	workFlow := &v1pb.Workflow{
		Name:      flowModel.Name,
		Assistant: flowModel.Assistant,
		InputLen:  flowModel.InputLen,
		WorkType:  v1pb.WorkType(v1pb.WorkType_value[flowModel.WorkType]),
		Urn:       utils.BuildUrn(utils.WorkflowUrnPrefix, flowModel.ID),
	}
	if flowModel.Pos != "" {
		workFlow.Pos = v1pb.FlowPos(v1pb.FlowPos_value[flowModel.Pos])
	} else {
		workFlow.Pos = v1pb.FlowPos_FLOW_POS_UNKOWN
	}

	if flowModel.GranaryScope != "" {
		workFlow.GranaryScope = v1pb.GranaryScope(v1pb.GranaryScope_value[flowModel.GranaryScope])
	} else {
		workFlow.GranaryScope = v1pb.GranaryScope_UNUSE_GRANARY
	}

	if flowModel.Granaries != "{}" {
		err := json.Unmarshal([]byte(flowModel.Granaries), &workFlow.Granaries)
		if err != nil {
			return nil, err
		}
	}

	s.buildWorkFlowNext(ctx, workFlow, flowModel.ID)
	return workFlow, nil

}

func (s *APIV1Service) buildWorkFlowNext(ctx context.Context, workflow *v1pb.Workflow, flowId int32) error {
	if workflow == nil {
		return nil
	}

	relations, err := s.Gds.Handler.ListWorkflowRelation(ctx, &apb.WorkflowRelation{
		FlowId: flowId,
	})
	if err != nil {
		return err
	}

	for _, relation := range relations {
		flowModel, err := s.Gds.Handler.GetWorkflow(ctx, &apb.FindWorkflow{
			Id: relation.NextFlowID,
		})
		if err != nil {
			return err
		}
		next, err := s.buildWorkFlow(ctx, flowModel)
		if err != nil {
			return err
		}

		workflow.Next = append(workflow.Next, next)
	}
	return nil
}

func (s *APIV1Service) DeleteWorkflow(ctx context.Context, request *v1pb.DeleteWorkflowRequest) (*emptypb.Empty, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}

	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}
	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() && currentUser.Role != cpb.Role_TENANT_ADMIN.String() {
		return nil, status.Errorf(codes.PermissionDenied, "permission denied")
	}

	id, err := utils.ExtractIDFromUrn(utils.WorkflowUrnPrefix, request.Urn)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get workflow id: %v", err)
	}

	err = s.deleteWorkflow(ctx, id)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to delete workflow: %v", err)
	}

	return nil, nil
}

func (s *APIV1Service) deleteWorkflow(ctx context.Context, flowId int32) error {

	relations, err := s.Gds.Handler.ListWorkflowRelation(ctx, &apb.WorkflowRelation{FlowId: flowId})
	if err != nil {
		return err
	}

	for _, relation := range relations {
		err = s.Gds.Handler.DeleteWorkflowRelaton(ctx, &apb.WorkflowRelation{
			FlowId:     flowId,
			NextFlowId: relation.NextFlowID,
		})

		if err != nil {
			return err
		}
		s.deleteWorkflow(ctx, relation.NextFlowID)
	}

	err = s.Gds.Handler.DeleteWorkflow(ctx, flowId)
	if err != nil {
		return err
	}

	return nil
}

func (s *APIV1Service) ListWorkflows(ctx context.Context, request *v1pb.ListWorkflowsRequest) (*v1pb.ListWorkflowsResponse, error) {
	currentUser, err := s.GetCurrentUser(ctx)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
	}
	find := &apb.FindWorkflow{
		Pos: v1pb.FlowPos_FLOW_HEAD.String(),
	}

	var limit, offset int32

	if request.PageToken != nil {
		limit = request.PageToken.Limit
		offset = request.PageToken.Offset
	} else {
		limit = request.PageSize
	}

	if limit <= 0 {
		limit = DefaultPageSize
	}

	limitPlusOne := limit
	find.Limit = limitPlusOne
	find.Offset = offset

	if currentUser.Role != cpb.Role_HOST.String() && currentUser.Role != cpb.Role_ADMIN.String() {
		find.TenantId = currentUser.TenantID
	}

	flows, total, err := s.Gds.Handler.ListWorkflows(ctx, find)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to list workflows: %v", err)
	}

	flowMsg := &v1pb.ListWorkflowsResponse{TotalSize: total}

	for _, flow := range flows {
		f, err := s.buildWorkFlow(ctx, flow)
		if err != nil {
			return nil, status.Errorf(codes.Internal, "failed to build workflow: %v", err)
		}
		flowMsg.Workflows = append(flowMsg.Workflows, f)
	}

	return flowMsg, nil
}
